#include "precompiled.h"
#include "Common.h"
#include "ModelLoader.h"

namespace ModelLoader{


	// hardcoded model paths.
	static const std::string basepath = "../models/BLEND/";
	static const std::string modelname = "Suzanne_248.blend";

	// the global Assimp scene object
	const aiScene* scene = NULL;
	GLuint scene_list = 0;
	struct aiVector3D scene_min, scene_max, scene_center;

	// Create an instance of the Importer class
	Assimp::Importer importer;

	// We want a single list of all the vertices in the mesh.
	std::vector<Vertex_PNT> vertices;

	// We want a list for each index group.
	std::vector<Index_Transform_Pair*> mesh_indices;

	// Test out mesh data buffering.
	GLuint	vao[1], vertex_buffer[1];
	std::vector<GLuint*> index_buffers;

	void createAILogger()
	{
		//Assimp::Logger::LogSeverity severity = Assimp::Logger::NORMAL;
		Assimp::Logger::LogSeverity severity = Assimp::Logger::VERBOSE;

		// Create a logger instance for Console Output
		Assimp::DefaultLogger::create("",severity, aiDefaultLogStream_STDOUT);

		// Create a logger instance for File Output (found in project folder or near .exe)
		Assimp::DefaultLogger::create("assimp_log.txt",severity, aiDefaultLogStream_FILE);

		// Now I am ready for logging my stuff
		Assimp::DefaultLogger::get()->info("this is my info-call");
	}

	void destroyAILogger()
	{
		// Kill it after the work is done
		Assimp::DefaultLogger::kill();
	}

	void logInfo(std::string logString)
	{
		//Will add message to File with "info" Tag
		Assimp::DefaultLogger::get()->info(logString.c_str());
	}

	void logDebug(const char* logString)
	{
		//Will add message to File with "debug" Tag
		Assimp::DefaultLogger::get()->debug(logString);
	}


	bool Import3DFromFile( const std::string& pFile)
	{
		//check if file exists
		std::ifstream fin(pFile.c_str());
		if(!fin.fail())
		{
			fin.close();
		}
		else
		{
			printError("Couldn't open file: ","ERROR");
			logInfo( importer.GetErrorString());
			return false;
		}

		scene = importer.ReadFile( pFile, aiProcessPreset_TargetRealtime_Quality);

		// If the import failed, report it
		if( !scene)
		{
			logInfo( importer.GetErrorString());
			return false;
		}

		// Now we can access the file's contents.
		logInfo("Import of scene " + pFile + " succeeded.");

		// We're done. Everything will be cleaned up by the importer destructor
		return true;
	}

	//	Deletes all temporary mesh data.
	void clear_meshdata(){

	}


	void AiTransformTo_Mat4(aiMatrix4x4 const &aim, glm::mat4 &idm){
		idm = glm::mat4(
			aim.a1, aim.a2, aim.a3, aim.a3,	//	x
			aim.b1, aim.b2, aim.b3, aim.b3, //	y
			aim.c1, aim.c2, aim.c3, aim.c3, //	z
			aim.d1, aim.d2, aim.d3, aim.d3 //	w
			);
	}

	void LoadAiScene_ToMeshData (const struct aiScene *sc, const struct aiNode* nd)
	{
		unsigned int i;
		unsigned int n=0, t;
		struct aiMatrix4x4 m = nd->mTransformation;

		// Create an index transform pair.
		Index_Transform_Pair *it_pair = new Index_Transform_Pair;

		// Capture the node transform.
		AiTransformTo_Mat4(nd->mTransformation,it_pair->transform);

		// draw all meshes assigned to this node
		for (; n < nd->mNumMeshes; ++n)
		{
			const struct aiMesh* mesh = scene->mMeshes[nd->mMeshes[n]];


			// Each mesh has an index group. Lets capture that.
			// Lets say that since each face is assumed to be triangular, a face is 3 indices.
			std::vector<GLushort> indices;

			//apply_material(sc->mMaterials[mesh->mMaterialIndex]);

			// Grab every vertex from the mesh.
			for(i = 0; i < mesh->mNumVertices; i++){

				if(mesh->mNormals != NULL){
					if(mesh->HasTextureCoords(0))		//HasTextureCoords(texture_coordinates_set)
					{
						// Capture the vertex w/ texture coordinates.
						GLfloat px = mesh->mVertices[i].x;
						GLfloat py = mesh->mVertices[i].y;
						GLfloat pz = mesh->mVertices[i].z;
						GLfloat nx = mesh->mNormals[i].x;
						GLfloat ny = mesh->mNormals[i].y;
						GLfloat nz = mesh->mNormals[i].z;
						GLfloat s = mesh->mTextureCoords[0][i].x;
						GLfloat t = 1 - mesh->mTextureCoords[0][i].y;
						Vertex_PNT vert = {
							px,py,pz,
							nx,ny,nz,
							s,t
						};
						vertices.push_back(vert);
					}
					else{
						// Capture the vertex w/o texture coordinates.
						GLfloat px = mesh->mVertices[i].x;
						GLfloat py = mesh->mVertices[i].y;
						GLfloat pz = mesh->mVertices[i].z;
						GLfloat nx = mesh->mNormals[i].x;
						GLfloat ny = mesh->mNormals[i].y;
						GLfloat nz = mesh->mNormals[i].z;
						GLfloat s = 0;
						GLfloat t = 0;
						Vertex_PNT vert = {
							px,py,pz,
							nx,ny,nz,
							s,t
						};
						vertices.push_back(vert);
					}
				}
			}
			std::cout << "Captured vertices count: " << vertices.size() << std::endl;

			// Collect our indices.
			for (t = 0; t < mesh->mNumFaces; ++t) {
				const struct aiFace* face = &mesh->mFaces[t];

				if(face->mNumIndices != 3){
					printError("ERROR: ","Model not composed of triangles.");
					return;
				}


				for(i = 0; i < face->mNumIndices; i++)		// go through all vertices in face
				{
					// Capture this index.
					indices.push_back( static_cast<GLushort>( face->mIndices[i] ) );
				}

			}
			std::cout << "Captured indices count: " << indices.size() << std::endl;

			// Allocate space for our indices transform pair index array.
			it_pair->indices.resize(indices.size());
			// Copy the index data.
			it_pair->indices.assign( indices.begin(), indices.begin()+indices.size());

			// Add the pair to our list of them.
			mesh_indices.push_back(it_pair);
		}


		// record all children.
		for (n = 0; n < nd->mNumChildren; ++n)
		{
			LoadAiScene_ToMeshData(sc, nd->mChildren[n]);
		}
	}

	void MeshDataTo_Buffers(std::vector<Vertex_PNT> &vertex_data, std::vector<GLushort> &index_data){

		// Setup the vertex buffer data.
		// Get OpenGL to create a single vertex buffer object for us.
		glGenBuffers(1, &vertex_buffer[0]);
		// "Activate" our vertex buffer.
		glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);
		// Allocate the space required for our data in the vertex buffer.
		int numVertices = vertex_data.size();
		glBufferData(GL_ARRAY_BUFFER, numVertices*sizeof(Vertex_PNT), NULL , GL_STATIC_DRAW);
		// Submit our data to the graphics device.
		glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Vertex_PNT), &(vertex_data.at(0)) );

		// Index setup
		GLuint indexbuff[1];
		glGenBuffers(1, indexbuff);
		// For our indices we work with element array buffers.
		int numIndices = index_data.size();
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexbuff[0]);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices*sizeof(GLushort), NULL, GL_STATIC_DRAW);
		glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*sizeof(GLushort), &index_data.at(0));

		// By this point all of our data should be on the graphics device.

		// VAO setup.
		// The vertex array object works (in it's utility) like a display list.
		// We activate it, set some features of our vbo's, and deactivate it.
		// At a later date, we can just activate this vao and operate on all this
		// sweet vertex buffer data.
		glGenVertexArrays(1, &vao[0]);
		glBindVertexArray(vao[0]);

		// Bind the vertex buffer and setup pointers for the VAO.
		// This is setting the vertex buffer for the vao.
		glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);
		// We want to plug our vertex data into our shader's attributes. The first
		// argument here is the attribute to feed that data into. The first shader
		// attibute is at 0, the next at 1, etc...Here we have position at 0,
		// nothing at 1, nothing at 2, color at 3.
		// The offset is straight forward, and basically tells OpenGL how far from
		// the start of the vertex we're at our chunk of data is. If position is first,
		// and is made up of 3 vertices, then thats a space of float_size * 3. This is
		// how far our next data chunk, color, is from the start of the vertex.
		glVertexAttribPointer(ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex_PNT), BUFFER_OFFSET(0));
		glVertexAttribPointer(ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex_PNT), BUFFER_OFFSET(sizeof(GLfloat)*3));
		glVertexAttribPointer(ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex_PNT), BUFFER_OFFSET(sizeof(GLfloat)*6));
		// For this vbo, tell our vao that the following attributes are active/not-active.
		glEnableVertexAttribArray(0);
		glEnableVertexAttribArray(1);
		glEnableVertexAttribArray(2);
		glDisableVertexAttribArray(3);

		// Bind the index buffer for the VAO.
		// This is setting the index buffer to use with this vao.
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexbuff[0]);

		// Add an index buffer.
		index_buffers.push_back(indexbuff);

		// Cleanup.
		// This doesn't delete anything, but it does unset our states to prevent
		//	any thing getting muddled.
		glBindVertexArray(0);
		glDisableVertexAttribArray(0);
		glBindBuffer(GL_ARRAY_BUFFER,0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
	}

	void load_myModel(const char* path, Model &model){
		// Clear the results from our previous load.
		vertices.clear();
		mesh_indices.clear();
		index_buffers.clear();

		Import3DFromFile(path);
		LoadAiScene_ToMeshData(scene,scene->mRootNode);
		std::cout << "Loaded model data." << std::endl;

		MeshDataTo_Buffers(vertices, mesh_indices.at(0)->indices);
		std::cout << "Buffered model data." << std::endl;

		model.Set_Vertices( vertices );
		model.Set_vao(vao[0]);
		model.Set_numIndices(mesh_indices.at(0)->indices.size());
	}

	void create_plane(Model &model){

		Vertex_PNT verts[4];

		// V0 - bottom left
		verts[0].x = -1.0f;
		verts[0].y = -1.0f;
		verts[0].z = 0.0f;
		verts[0].nx = 0.0f;
		verts[0].ny = 0.0f;
		verts[0].nz = -1.0f;
		verts[0].s = 0.0f;
		verts[0].t = 0.0f;

		// V1 - bottom right
		verts[1].x = 1.0f;
		verts[1].y = -1.0f;
		verts[1].z = 0.0f;
		verts[1].nx = 0.0f;
		verts[1].ny = 0.0f;
		verts[1].nz = -1.0f;
		verts[1].s = 1.0f;
		verts[1].t = 0.0f;

		// V2 - top right
		verts[2].x = 1.0f;
		verts[2].y = 1.0f;
		verts[2].z = 0.0f;
		verts[2].nx = 0.0f;
		verts[2].ny = 0.0f;
		verts[2].nz = -1.0f;
		verts[2].s = 1.0f;
		verts[2].t = 1.0f;

		// V3 - top left
		verts[3].x = -1.0f;
		verts[3].y = 1.0f;
		verts[3].z = 0.0f;
		verts[3].nx = 0.0f;
		verts[3].ny = 0.0f;
		verts[3].nz = -1.0f;
		verts[3].s = 0.0f;
		verts[3].t = 1.0f;


		// Setup the vertex buffer data.
		// Get OpenGL to create a single vertex buffer object for us.
		//GLuint vbo_vertex[1];
		glGenBuffers(1, &vertex_buffer[0]);
		// "Activate" our vertex buffer.
		glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);
		// Allocate the space required for our data in the vertex buffer.
		GLint numVerts = 4;
		glBufferData(GL_ARRAY_BUFFER, numVerts*sizeof(Vertex_PNT), NULL , GL_STATIC_DRAW);
		// Submit our data to the graphics device.
		glBufferSubData(GL_ARRAY_BUFFER, 0, numVerts*sizeof(Vertex_PNT), verts);


		// Index setup
		GLushort indices[6];
		indices[0]=0;
		indices[1]=2;
		indices[2]=3;
		indices[3]=0;
		indices[4]=1;
		indices[5]=2;
		GLuint index_buffer[1];
		glGenBuffers(1, &index_buffer[0]);

		GLint numIndices = 6;
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer[0]);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices*sizeof(GLushort), NULL, GL_STATIC_DRAW);
		glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*sizeof(GLushort), indices);

		// By this point all of our data should be on the graphics device.

		GLuint vao[1];
		glGenVertexArrays(1, &vao[0]);
		glBindVertexArray(vao[0]);


		glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);

		glVertexAttribPointer(ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex_PNT), BUFFER_OFFSET(0));
		glVertexAttribPointer(ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex_PNT), BUFFER_OFFSET(sizeof(GLfloat)*3));
		glVertexAttribPointer(ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex_PNT), BUFFER_OFFSET(sizeof(GLfloat)*6));
		// For this vbo, tell our vao that the following attributes are active/not-active.
		glEnableVertexAttribArray(0);
		glEnableVertexAttribArray(1);
		glEnableVertexAttribArray(2);
		glDisableVertexAttribArray(3);

		// Bind the index buffer for the VAO.
		// This is setting the index buffer to use with this vao.
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer[0]);

		// Cleanup.
		// This doesn't delete anything, but it does unset our states to prevent
		//	any thing getting muddled.
		glBindVertexArray(0);
		glDisableVertexAttribArray(0);
		glBindBuffer(GL_ARRAY_BUFFER,0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);

		// Set the model's vao and indices.
		model.Set_vao(vao[0]);
		model.Set_numIndices(numIndices);
		//model.Set_VertexBuffer(vertex_buffer[0]);
		//model.Add_IndexBuffer(index_buffer);
	}

}